home *** CD-ROM | disk | FTP | other *** search
- /*
- * Routines to compress and uncompess tcp packets (for transmission
- * over low speed serial lines.
- *
- * Copyright (c) 1989 Regents of the University of California.
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms are permitted
- * provided that the above copyright notice and this paragraph are
- * duplicated in all such forms and that any documentation,
- * advertising materials, and other materials related to such
- * distribution and use acknowledge that the software was developed
- * by the University of California, Berkeley. The name of the
- * University may not be used to endorse or promote products derived
- * from this software without specific prior written permission.
- * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
- * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
- * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
- *
- * Van Jacobson (van@helios.ee.lbl.gov), Dec 31, 1989:
- * - Initial distribution.
- */
- /*
- * modified for KA9Q Internet Software Package by
- * Katie Stevens (dkstevens@ucdavis.edu)
- * University of California, Davis
- * Computing Services
- * - 01-31-90 initial adaptation
- * PPP.05 02-15-90 [ks]
- * PPP.08 05-02-90 [ks] use PPP protocol field to signal compression
- * PPP.15 09-90 [ks] improve mbuf handling
- */
-
- #define ntohl(x) (x)
- #define ntohs(x) (x)
- #define htonl(x) (x)
- #define htons(x) (x)
-
- #ifndef lint
- static char rcsid[] = "$Header: slcompress.c,v 1.19 89/12/31 08:52:59 van Exp $";
- #endif
-
- #ifndef AMIGA
- #include <mem.h>
- #endif
- #include "global.h"
- #include "mbuf.h"
- #include "internet.h"
- #include "slcompre.h"
-
- #ifndef SL_NO_STATS
- #define INCR(counter) ++comp->counter;
- #else
- #define INCR(counter)
- #endif
-
- #define BCMP(p1, p2, n) memcmp((char *)(p1), (char *)(p2), (int)(n))
- #define BCOPY(p1, p2, n) memcpy((char *)(p2), (char *)(p1), (int)(n))
- #ifndef KERNEL
- #define ovbcopy bcopy
- #endif
-
- #ifdef AMIGA
- #define ntohs(x) (x)
- #define ntohl(x) (x)
- #else
- /*
- * network-to-host byte order translators for 16bit and 32bit integers
- */
- #define ntohs(x) get16((char *)&(x))
- #define ntohl(x) get32((char *)&(x))
- /*
- * host-to-network byte order translators for 16bit and 32bit integers
- */
- int16
- htons(hostx)
- int16 hostx;
- {
- int16 netx;
-
- netx = ((hostx >> 8) & 0x00ff);
- netx |= ((hostx << 8) & 0xff00);
- return(netx);
- }
- int32
- htonl(hostl)
- int32 hostl;
- {
- int32 netl;
-
- netl = ((hostl >> 24) & 0x000000ff);
- netl |= ((hostl >> 8) & 0x0000ff00);
- netl |= ((hostl << 8) & 0x00ff0000);
- netl |= ((hostl << 24) & 0xff000000);
- return(netl);
- }
- #endif
-
- /*******************************************/
-
- void
- sl_compress_init(comp)
- struct slcompress *comp;
- {
- register int16 i;
- register struct cstate *tstate = comp->tstate;
-
- memset((char *)comp, 0, sizeof(*comp));
- for (i = MAX_STATES - 1; i > 0; --i) {
- tstate[i].cs_id = i;
- tstate[i].cs_next = &tstate[i - 1];
- }
- tstate[0].cs_next = &tstate[MAX_STATES - 1];
- tstate[0].cs_id = 0;
- comp->last_cs = &tstate[0];
- comp->last_recv = 255;
- comp->last_xmit = 255;
- }
-
-
- /* ENCODE encodes a number that is known to be non-zero. ENCODEZ
- * checks for zero (since zero has to be encoded in the long, 3 byte
- * form).
- */
- #define ENCODE(n) { \
- if ((int16)(n) >= 256) { \
- *cp++ = 0; \
- cp = put16(cp,(int16)(n)); \
- } else { \
- *cp++ = (n); \
- } \
- }
- #define ENCODEZ(n) { \
- if ((int16)(n) >= 256 || (int16)(n) == 0) { \
- *cp++ = 0; \
- cp = put16(cp,(int16)(n)); \
- } else { \
- *cp++ = (n); \
- } \
- }
-
- #define DECODEL(f) { \
- if (pullup(bufp, tmpbuf, 1) != 1) {\
- INCR(sls_errorin)\
- return (0);\
- }\
- if (tmpbuf[0] == 0) {\
- if (pullup(bufp, tmpbuf, 2) != 2) {\
- INCR(sls_errorin)\
- return (0);\
- }\
- (f) = htonl(ntohl(f) + (unsigned long)get16(tmpbuf)); \
- } else { \
- (f) = htonl(ntohl(f) + (unsigned long)tmpbuf[0]); \
- } \
- }
-
- #define DECODES(f) { \
- if (pullup(bufp, tmpbuf, 1) != 1) {\
- INCR(sls_errorin)\
- return (0);\
- }\
- if (tmpbuf[0] == 0) {\
- if (pullup(bufp, tmpbuf, 2) != 2) {\
- INCR(sls_errorin)\
- return (0);\
- }\
- (f) = htons(ntohs(f) + (unsigned long)get16(tmpbuf)); \
- } else { \
- (f) = htons(ntohs(f) + (unsigned long)tmpbuf[0]); \
- } \
- }
-
- #define DECODEU(f) { \
- if (pullup(bufp, tmpbuf, 1) != 1) {\
- INCR(sls_errorin)\
- return (0);\
- }\
- if (tmpbuf[0] == 0) {\
- if (pullup(bufp, tmpbuf, 2) != 2) {\
- INCR(sls_errorin)\
- return (0);\
- }\
- (f) = htons(get16(tmpbuf)); \
- } else { \
- (f) = htons((int16)tmpbuf[0]); \
- } \
- }
-
-
- unsigned char
- sl_compress_tcp(bpp, iph, comp, compress_cid)
- struct mbuf **bpp;
- register struct iphdr *iph;
- struct slcompress *comp;
- int compress_cid;
- {
- register struct cstate *cs = comp->last_cs->cs_next;
- register int16 hlen = (iph->ipvers_hlen & 0x0f);
- register struct tcphdr *oth;
- register struct tcphdr *th;
- register unsigned long deltaS, deltaA;
- register int16 changes = 0;
- unsigned char new_seq[16];
- register unsigned char *cp = new_seq;
- register int16 iphlen = hlen;
- register int16 tcphlen;
- struct mbuf *bp;
-
- bp = *bpp;
- /*
- * Bail if this is an IP fragment or if the TCP packet isn't
- * `compressible' (i.e., ACK isn't set or some other control bit is
- * set). (We assume that the caller has already made sure the
- * packet is IP proto TCP).
- */
- if ((iph->ip_fragoff & htons(0x3fff)) ||
- ((bp->cnt != (hlen << 2)) && (bp->cnt < 40))) {
- /* Send as regular IP */
- INCR(sls_asistcp);
- return (SL_TYPE_IP);
- }
-
- /* TCP header may be in next mbuf */
- if (bp->cnt != (hlen << 2))
- th = (struct tcphdr *)(bp->data+(hlen << 2));
- else
- th = (struct tcphdr *)(bp->next->data);
- if ((th->th_flags & (TH_SYN|TH_FIN|TH_RST|TH_ACK)) != TH_ACK) {
- /* TCP connection stuff; send as regular IP */
- INCR(sls_asistcp);
- return (SL_TYPE_IP);
- }
-
- /*
- * Packet is compressible -- we're going to send either a
- * COMPRESSED_TCP or UNCOMPRESSED_TCP packet. Either way we need
- * to locate (or create) the connection state. Special case the
- * most recently used connection since it's most likely to be used
- * again & we don't have to do any reordering if it's used.
- */
- if (iph->ip_source != cs->cs_ip.ip_source ||
- iph->ip_dest != cs->cs_ip.ip_dest ||
- *(int32 *)th != ((int32 *)&cs->cs_ip)[(cs->cs_ip.ipvers_hlen & 0x0f)]) {
- /*
- * Wasn't the first -- search for it.
- *
- * States are kept in a circularly linked list with
- * last_cs pointing to the end of the list. The
- * list is kept in lru order by moving a state to the
- * head of the list whenever it is referenced. Since
- * the list is short and, empirically, the connection
- * we want is almost always near the front, we locate
- * states via linear search. If we don't find a state
- * for the datagram, the oldest state is (re-)used.
- */
- register struct cstate *lcs;
- register struct cstate *lastcs = comp->last_cs;
-
- do {
- lcs = cs; cs = cs->cs_next;
- INCR(sls_searches)
- if (iph->ip_source == cs->cs_ip.ip_source
- && iph->ip_dest == cs->cs_ip.ip_dest
- && *(int32 *)th == ((int32 *)&cs->cs_ip)[(cs->cs_ip.ipvers_hlen & 0x0f)])
- goto found;
- } while (cs != lastcs);
-
- /*
- * Didn't find it -- re-use oldest cstate. Send an
- * uncompressed packet that tells the other side what
- * connection number we're using for this conversation.
- * Note that since the state list is circular, the oldest
- * state points to the newest and we only need to set
- * last_cs to update the lru linkage.
- */
- INCR(sls_misses)
- comp->last_cs = lcs;
- tcphlen = ((th->th_off & 0xf0) >> 4);
- hlen += tcphlen;
- hlen <<= 2;
- goto uncompressed;
-
- found:
- /*
- * Found it -- move to the front on the connection list.
- */
- if (cs == lastcs)
- comp->last_cs = lcs;
- else {
- lcs->cs_next = cs->cs_next;
- cs->cs_next = lastcs->cs_next;
- lastcs->cs_next = cs;
- }
- }
-
- /*
- * Make sure that only what we expect to change changed. The first
- * line of the `if' checks the IP protocol version, header length &
- * type of service. The 2nd line checks the "Don't fragment" bit.
- * The 3rd line checks the time-to-live and protocol (the protocol
- * check is unnecessary but costless). The 4th line checks the TCP
- * header length. The 5th line checks IP options, if any. The 6th
- * line checks TCP options, if any. If any of these things are
- * different between the previous & current datagram, we send the
- * current datagram `uncompressed'.
- */
- oth = (struct tcphdr *)&((int32 *)&cs->cs_ip)[hlen];
- deltaS = hlen;
- tcphlen = ((th->th_off & 0xf0) >> 4);
- hlen += tcphlen;
- hlen <<= 2;
-
- if (((int16 *)iph)[0] != ((int16 *)&cs->cs_ip)[0] ||
- ((int16 *)iph)[3] != ((int16 *)&cs->cs_ip)[3] ||
- ((int16 *)iph)[4] != ((int16 *)&cs->cs_ip)[4] ||
- (th->th_off & 0xf0) != (oth->th_off & 0xf0) ||
- (deltaS > 5 &&
- BCMP(iph + 1, &cs->cs_ip + 1, (deltaS - 5) << 2)) ||
- (tcphlen > 5 &&
- BCMP(th + 1, oth + 1, (tcphlen - 5) << 2)))
- goto uncompressed;
-
- /*
- * Figure out which of the changing fields changed. The
- * receiver expects changes in the order: urgent, window,
- * ack, seq (the order minimizes the number of temporaries
- * needed in this section of code).
- */
- if (th->th_flags & TH_URG) {
- deltaS = ntohs(th->th_urgpt);
- ENCODEZ(deltaS);
- changes |= NEW_U;
- } else if (th->th_urgpt != oth->th_urgpt)
- /* argh! URG not set but urp changed -- a sensible
- * implementation should never do this but RFC793
- * doesn't prohibit the change so we have to deal
- * with it. */
- goto uncompressed;
-
- if ((deltaS = (int16)(ntohs(th->th_win) - ntohs(oth->th_win))) != 0) {
- ENCODE(deltaS);
- changes |= NEW_W;
- }
-
- if ((deltaA = ntohl(th->th_ack) - ntohl(oth->th_ack)) != 0L) {
- if (deltaA > 0x0000ffff)
- goto uncompressed;
- ENCODE(deltaA);
- changes |= NEW_A;
- }
-
- if ((deltaS = ntohl(th->th_seq) - ntohl(oth->th_seq)) != 0L) {
- if (deltaS > 0x0000ffff)
- goto uncompressed;
- ENCODE(deltaS);
- changes |= NEW_S;
- }
-
- switch(changes) {
-
- case 0:
- /*
- * Nothing changed. If this packet contains data and the
- * last one didn't, this is probably a data packet following
- * an ack (normal on an interactive connection) and we send
- * it compressed. Otherwise it's probably a retransmit,
- * retransmitted ack or window probe. Send it uncompressed
- * in case the other side missed the compressed version.
- */
- if (iph->ip_totlen != cs->cs_ip.ip_totlen &&
- ntohs(cs->cs_ip.ip_totlen) == hlen)
- break;
- /* (fall through) */
-
- case SPECIAL_I:
- case SPECIAL_D:
- /*
- * actual changes match one of our special case encodings --
- * send packet uncompressed.
- */
- goto uncompressed;
-
- case NEW_S|NEW_A:
- if (deltaS == deltaA &&
- deltaS == ntohs(cs->cs_ip.ip_totlen) - hlen) {
- /* special case for echoed terminal traffic */
- changes = SPECIAL_I;
- cp = new_seq;
- }
- break;
-
- case NEW_S:
- if (deltaS == ntohs(cs->cs_ip.ip_totlen) - hlen) {
- /* special case for data xfer */
- changes = SPECIAL_D;
- cp = new_seq;
- }
- break;
- }
-
- deltaS = ntohs(iph->ip_id) - ntohs(cs->cs_ip.ip_id);
- if (deltaS != 1) {
- ENCODEZ(deltaS);
- changes |= NEW_I;
- }
- if (th->th_flags & TH_PUSH)
- changes |= TCP_PUSH_BIT;
- /*
- * Grab the cksum before we overwrite it below. Then update our
- * state with this packet's header.
- */
- deltaA = ntohs(th->th_csum);
- /* ip and tcp headers may be in separate mbufs, copy to one array */
- iphlen <<= 2;
- tcphlen <<= 2;
- BCOPY(iph, &cs->cs_ip, iphlen);
- BCOPY(th, &(cs->cs_hdr[iphlen]), tcphlen);
-
- /*
- * We want to use the original packet as our compressed packet.
- * (cp - new_seq) is the number of bytes we need for compressed
- * sequence numbers. In addition we need one byte for the change
- * mask, one for the connection id and two for the tcp checksum.
- * So, (cp - new_seq) + 4 bytes of header are needed. hlen is how
- * many bytes of the original packet to toss so subtract the two to
- * get the new packet size.
- */
- deltaS = cp - new_seq;
- if (compress_cid == 0 || comp->last_xmit != cs->cs_id) {
- comp->last_xmit = cs->cs_id;
- hlen -= deltaS + 4;
- bp = *bpp;
- if (bp->cnt > hlen) {
- bp->data += hlen;
- bp->cnt -= hlen;
- } else {
- pullup(bpp, NULLCHAR, hlen);
- bp = *bpp;
- }
- cp = bp->data;
- *cp++ = changes | NEW_C;
- *cp++ = cs->cs_id;
- } else {
- hlen -= deltaS + 3;
- bp = *bpp;
- if (bp->cnt > hlen) {
- bp->data += hlen;
- bp->cnt -= hlen;
- } else {
- pullup(bpp, NULLCHAR, hlen);
- bp = *bpp;
- }
- cp = bp->data;
- *cp++ = changes;
- }
- *cp++ = deltaA >> 8;
- *cp++ = deltaA;
- BCOPY(new_seq, cp, deltaS);
- INCR(sls_compressed)
- return (SL_TYPE_COMPRESSED_TCP); /* send as TYPE_COMPRESSED_TCP */
-
- /*
- * Update connection state cs & send uncompressed packet ('uncompressed'
- * means a regular ip/tcp packet but with the 'conversation id' we hope
- * to use on future compressed packets in the protocol field).
- */
- uncompressed:
- /* ip and tcp headers are in separate mbufs, copy to one array */
- iphlen <<= 2;
- tcphlen <<= 2;
- BCOPY(iph, &cs->cs_ip, iphlen);
- BCOPY(th, &(cs->cs_hdr[iphlen]), tcphlen);
- iph->ip_protocol = cs->cs_id;
- comp->last_xmit = cs->cs_id;
- INCR(sls_uncompressed);
- return (SL_TYPE_UNCOMPRESSED_TCP); /* send as TYPE_UNCOMPRESSED_TCP */
- }
-
-
- int
- sl_uncompress_tcp(bufp, len, type, comp)
- struct mbuf **bufp;
- int len;
- int16 type;
- struct slcompress *comp;
- {
- register int16 hlen;
- register unsigned long changes;
- register struct tcphdr *th;
- register struct cstate *cs;
- register struct iphdr *iph;
- register int16 iphlen;
- int16 tmplen;
- unsigned char tmpbuf[2];
- struct mbuf *newp;
-
- switch (type) {
-
- case SL_TYPE_UNCOMPRESSED_TCP:
- iph = (struct iphdr *) (*bufp)->data;
- if (iph->ip_protocol >= MAX_STATES)
- goto bad;
- cs = &comp->rstate[comp->last_recv = iph->ip_protocol];
- comp->flags &=~ SLF_TOSS;
- iph->ip_protocol = TCP_PTCL;
- hlen = (iph->ipvers_hlen & 0x0f);
- iphlen = hlen << 2;
- newp = *bufp;
- if (iphlen > newp->cnt) {
- if (newp->next == NULLBUF) {
- INCR(sls_errorin)
- return (0);
- }
- tmplen = ((struct tcphdr *)&(newp->next->data[iphlen - (*bufp)->cnt]))->th_off;
- } else {
- tmplen = ((struct tcphdr *)&((int32 *)iph)[hlen])->th_off;
- }
- hlen += ((tmplen & 0xf0) >> 4);
- hlen <<= 2;
- if (hlen > newp->cnt) {
- BCOPY(iph, &cs->cs_ip, newp->cnt);
- BCOPY(newp->next, &(cs->cs_hdr[newp->cnt]), (hlen - newp->cnt));
- } else {
- BCOPY(iph, &cs->cs_ip, hlen);
- }
- cs->cs_ip.ip_csum = 0;
- cs->cs_hlen = hlen;
- INCR(sls_uncompressedin)
- return (len);
-
- default:
- goto bad;
-
- case SL_TYPE_COMPRESSED_TCP:
- break;
- }
- /* We've got a compressed packet. */
- INCR(sls_compressedin)
- if (pullup(bufp, tmpbuf, 1) != 1) {
- /* we must have dropped some characters (crc should detect
- * this but the old slip framing won't) */
- INCR(sls_errorin)
- return (0);
- }
- changes = tmpbuf[0];
- if (changes & NEW_C) {
- /* Make sure the state index is in range, then grab the state.
- * If we have a good state index, clear the 'discard' flag. */
- if (pullup(bufp, tmpbuf, 1) != 1)
- /* we must have dropped some characters (crc should detect
- * this but the old slip framing won't) */
- goto bad;
- if (tmpbuf[0] >= MAX_STATES)
- goto bad;
-
- comp->flags &=~ SLF_TOSS;
- comp->last_recv = tmpbuf[0];
- } else {
- /* this packet has an implicit state index. If we've
- * had a line error since the last time we got an
- * explicit state index, we have to toss the packet. */
- if (comp->flags & SLF_TOSS) {
- INCR(sls_tossed)
- return (0);
- }
- }
- cs = &comp->rstate[comp->last_recv];
- hlen = (cs->cs_ip.ipvers_hlen & 0x0f) << 2;
- th = (struct tcphdr *)&((unsigned char *)&cs->cs_ip)[hlen];
- if (pullup(bufp, tmpbuf, 2) != 2)
- /* we must have dropped some characters (crc should detect
- * this but the old slip framing won't) */
- goto bad;
- th->th_csum = htons(get16(tmpbuf));
- if (changes & TCP_PUSH_BIT)
- th->th_flags |= TH_PUSH;
- else
- th->th_flags &=~ TH_PUSH;
-
- switch (changes & SPECIALS_MASK) {
- case SPECIAL_I:
- {
- register int16 i = ntohs(cs->cs_ip.ip_totlen) - cs->cs_hlen;
- th->th_ack = htonl(ntohl(th->th_ack) + i);
- th->th_seq = htonl(ntohl(th->th_seq) + i);
- }
- break;
-
- case SPECIAL_D:
- th->th_seq = htonl(ntohl(th->th_seq) + ntohs(cs->cs_ip.ip_totlen)
- - cs->cs_hlen);
- break;
-
- default:
- if (changes & NEW_U) {
- th->th_flags |= TH_URG;
- DECODEU(th->th_urgpt)
- } else
- th->th_flags &=~ TH_URG;
- if (changes & NEW_W)
- DECODES(th->th_win)
- if (changes & NEW_A)
- DECODEL(th->th_ack)
- if (changes & NEW_S)
- DECODEL(th->th_seq)
- break;
- }
- if (changes & NEW_I) {
- DECODES(cs->cs_ip.ip_id)
- } else
- cs->cs_ip.ip_id = htons(ntohs(cs->cs_ip.ip_id) + 1);
-
- /*
- * At this point, cp points to the first byte of data in the
- * packet. If we're not aligned on a 4-byte boundary, copy the
- * data down so the ip & tcp headers will be aligned. Then back up
- * cp by the tcp/ip header length to make room for the reconstructed
- * header (we assume the packet we were handed has enough space to
- * prepend 128 bytes of header). Adjust the length to account for
- * the new header & fill in the IP total length.
- */
- len = len_p(*bufp) + cs->cs_hlen;
- cs->cs_ip.ip_totlen = htons(len);
-
- newp = pushdown(*bufp, cs->cs_hlen);
- *bufp = newp;
- BCOPY((unsigned char *)&cs->cs_ip, (*bufp)->data, cs->cs_hlen);
-
- /* recompute the ip header checksum */
- {
- register int16 *bp = (int16 *)newp->data;
- for (changes = 0; hlen > 0; hlen -= 2)
- changes += *bp++;
- changes = (changes & 0xffff) + (changes >> 16);
- changes = (changes & 0xffff) + (changes >> 16);
- ((struct iphdr *)newp->data)->ip_csum = ~ changes;
- }
- return (len);
- bad:
- comp->flags |= SLF_TOSS;
- INCR(sls_errorin)
- return (0);
- }
-